package com.anb.net;

import com.anb.common.Log;
import com.anb.common.Task;
import com.anb.common.Utils;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;

public class Client extends NetObject {

    public Client(IRunner runner, Constructor<?> connectionConstructor, Object... connectionArgs) {
        super("*client*");
        this.runner = runner;
        this.connectionConstructor = connectionConstructor;
        this.connectionArgs = connectionArgs;
    }

    public Client(IRunner runner, Class<?> connectionClass, Object... connectionArgs) throws Exception {
        this(runner, Utils.matchConstructor(connectionClass, ConnectionBase.getArgTypes(connectionArgs)), connectionArgs);
    }

    private IRunner runner;

    private Constructor<?> connectionConstructor;
    private Object[] connectionArgs;

    private SocketChannel channel;
    public SocketChannel getChannel() {
        return channel;
    }

    private ConnectionBase connection;
    public ConnectionBase getConnection() {
        return connection;
    }

    public interface ConnectHandler {
        void success(ConnectionBase connection);
        void error(Exception e);
    }
    private ConnectHandler connectHandler;
    public void start(InetAddress remote, int port, ConnectHandler connectHandler) throws IOException {
        setAlias(String.format("%s:%s", remote.toString(), port));
        this.connectHandler = connectHandler;

        Log.Get().info(getAlias() + " connects");
        channel = SocketChannel.open();
        channel.configureBlocking(false);

        runner.register(this);

        InetSocketAddress isa = new InetSocketAddress(remote, port);
        channel.connect(isa);
    }
    @Override
    public void stop() {
        super.stop();
        runner.unregister(this);
        try {
            channel.close();
        } catch (IOException e) {
            Log.Get().error(getAlias() + " stop error", e);
        }
        if (connection != null) {
            connection.stop();
        }
    }

    protected void connected(SelectionKey key) {
        //runner.unregister(this);

        try {
            channel.finishConnect();
        } catch (final IOException e) {
            Log.Get().error(getAlias() + " connect error", e);
            stop();
            connectedError(connectHandler, e);
            return;
        }
        Log.Get().info(getAlias() + " connected");

        Object[] args = Utils.flat(runner, channel, connectionArgs);
        try {
            connection = (ConnectionBase)connectionConstructor.newInstance(args);
        } catch (Exception e) {
            Log.Get().error(getAlias() + " create connection error", e);
            stop();
            connectedError(connectHandler, e);
            return;
        }

        try {
            connection.start();
        } catch (IOException e) {
            Log.Get().error(getAlias() + " start connection error", e);
            stop();
            connectedError(connectHandler, e);
            return;
        }
        connectedSuccess(connectHandler, connection);
    }

    private void connectedSuccess(final ConnectHandler connectHandler, final ConnectionBase connection) {
        Task.run(new Runnable() {
            @Override
            public void run() {
                connectHandler.success(connection);
            }
        });
    }

    private void connectedError(final ConnectHandler connectHandler, final Exception e) {
        Task.run(new Runnable() {
            @Override
            public void run() {
                connectHandler.error(e);
            }
        });
    }
}
